#ifndef QHTTPCLIENT_H
#define QHTTPCLIENT_H

#include <QDebug>
#include <QThreadStorage>
#include <QNetworkReply>
#include <QNetworkAccessManager>

#include "QJsonSerialize.h"
#include "QJsonSerialize.h"
#include "QHttpSerialize.h"
#include "QFunctionTraits.h"

namespace QtNet {

class QHttpClient : public QObject
{
    Q_OBJECT
public:
    template<typename T>
    static void get(const QNetworkRequest &request, T callback);

    template <typename Class, typename Param1, typename Param2>
    static void get(const QNetworkRequest &request, Class *object, void (Class::*method)(Param1, Param2));

    template<typename T, typename Func>
    static void post(const QNetworkRequest &request, const T &data, Func callback);

    template <typename T, typename Class, typename Param1, typename Param2>
    static void post(const QNetworkRequest &request, const T &data, Class *object, void(Class::*method)(Param1, Param2));

    template<typename T, typename Func>
    static void put(const QNetworkRequest &request, const T &data, Func callback);

    template <typename T, typename Class, typename Param1, typename Param2>
    static void put(const QNetworkRequest& request, const T &data, Class *object, void(Class::*method)(Param1, Param2));

    template<typename T>
    static void deleteResource(const QNetworkRequest &request, T callback);

    template <typename Class, typename Param1, typename Param2>
    static void deleteResource(const QNetworkRequest &request, Class *object, void(Class::*method)(Param1, Param2));

private:
    static QNetworkAccessManager *getNetworkAccessManager();
    static void handleException(const nlohmann::json::exception &e, const QString &type="");

private:
    static QThreadStorage<QNetworkAccessManager*> threadStorage;
};

template<typename T>
void QHttpClient::get(const QNetworkRequest &request, T callback)
{
    using traits = QFunction_traits<typename std::decay<T>::type>;

    QNetworkReply *reply = getNetworkAccessManager()->get(request);
    connect(reply, &QNetworkReply::finished, [=]() {
        try {
            if (reply->error() == QNetworkReply::NoError) {
                typename traits::arg1_type data;
                QHttpContentType<typename traits::arg1_type>::fromByteArray(data, reply->readAll());
                callback(data, reply);
            } else {
                typename traits::arg1_type data;
                callback(data, reply);
                qDebug() << "get异常:" << reply->errorString();
            }
        } catch (const json::exception &e) {
            handleException(e, QString::fromStdString(std::string(typeid(typename traits::arg1_type).name())));
        } catch(...) {
            qDebug() << "未知异常";
        }
        reply->deleteLater();
    });
}

template <typename Class, typename Param1, typename Param2>
void QHttpClient::get(const QNetworkRequest &request, Class *object, void (Class::*method)(Param1, Param2))
{
    auto callback = [object, method](Param1 p1, Param2 p2) {
        (object->*method)(p1, p2);
    };
    get(request, callback);
}

template<typename T, typename Func>
void QHttpClient::post(const QNetworkRequest &request, const T &data, Func callback)
{
    using traits = QFunction_traits<typename std::decay<Func>::type>;

    try {
        QByteArray byteArray;
        QHttpContentType<T>::toByteArray(data, byteArray);

        QNetworkReply *reply = getNetworkAccessManager()->post(request, byteArray);
        connect(reply, &QNetworkReply::finished, [=]() {
            try {
                if (reply->error() == QNetworkReply::NoError) {
                    typename traits::arg1_type data;
                    QHttpContentType<typename traits::arg1_type>::fromByteArray(data, reply->readAll());
                    callback(data, reply);
                } else {
                    typename traits::arg1_type data;
                    callback(data, reply);
                    qDebug() << "post异常:" << reply->errorString();
                }
            } catch (const json::exception &e) {
                handleException(e, QString::fromStdString(std::string(typeid(typename traits::arg1_type).name())));
            } catch(...) {
                qDebug() << "未知异常";
            }
            reply->deleteLater();
        });
    } catch (const json::exception &e) {
        handleException(e, QString::fromStdString(std::string(typeid(T).name())));
    } catch(...) {
        qDebug() << "未知异常";
    }
}

template <typename T, typename Class, typename Param1, typename Param2>
void QHttpClient::post(const QNetworkRequest &request, const T &data, Class* object, void(Class::*method)(Param1, Param2))
{
    auto callback = [object, method](Param1 p1, Param2 p2) {
        (object->*method)(p1, p2);
    };
    post(request, data, callback);
}

template<typename T, typename Func>
void QHttpClient::put(const QNetworkRequest &request, const T &data, Func callback)
{
    using traits = QFunction_traits<typename std::decay<Func>::type>;

    try {
        QByteArray byteArray;
        QHttpContentType<T>::toByteArray(data, byteArray);

        QNetworkReply *reply = getNetworkAccessManager()->put(request, byteArray);
        connect(reply, &QNetworkReply::finished, [=]() {
            try {
                if (reply->error() == QNetworkReply::NoError) {
                    typename traits::arg1_type data;
                    QHttpContentType<typename traits::arg1_type>::fromByteArray(data, reply->readAll());
                    callback(data, reply);
                } else {
                    typename traits::arg1_type data;
                    callback(data, reply);
                    qDebug() << "put异常:" << reply->errorString();
                }
            } catch (const json::exception &e) {
                handleException(e, QString::fromStdString(std::string(typeid(typename traits::arg1_type).name())));
            } catch(...) {
                qDebug() << "未知异常";
            }
            reply->deleteLater();
        });
    } catch (const json::exception &e) {
        handleException(e, QString::fromStdString(std::string(typeid(T).name())));
    } catch(...) {
        qDebug() << "未知异常";
    }
}

template <typename T, typename Class, typename Param1, typename Param2>
void QHttpClient::put(const QNetworkRequest &request, const T &data, Class *object, void(Class::*method)(Param1, Param2))
{
    auto callback = [object, method](Param1 p1, Param2 p2) {
        (object->*method)(p1, p2);
    };
    put(request, data, callback);
}

template<typename T>
void QHttpClient::deleteResource(const QNetworkRequest &request, T callback)
{
    using traits = QFunction_traits<typename std::decay<T>::type>;

    QNetworkReply *reply = getNetworkAccessManager()->deleteResource(request);
    connect(reply, &QNetworkReply::finished, [=]() {
        try {
            if (reply->error() == QNetworkReply::NoError) {
                typename traits::arg1_type data;
                QHttpContentType<typename traits::arg1_type>::fromByteArray(data, reply->readAll());
                callback(data, reply);
            } else {
                typename traits::arg1_type data;
                callback(data, reply);
                qDebug() << "deleteResource异常:" << reply->errorString();
            }
        } catch (const json::exception &e) {
            handleException(e, QString::fromStdString(std::string(typeid(typename traits::arg1_type).name())));
        } catch(...) {
            qDebug() << "未知异常";
        }
        reply->deleteLater();
    });
}

template <typename Class, typename Param1, typename Param2>
void QHttpClient::deleteResource(const QNetworkRequest &request, Class *object, void(Class::*method)(Param1, Param2))
{
    auto callback = [object, method](Param1 p1, Param2 p2) {
        (object->*method)(p1, p2);
    };
    deleteResource(request, callback);
}

}

#endif // QHTTPCLIENT_H
